/*
 * Copyright (C) 2008 Eric Bottard
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.code.liquidform;

import static com.google.code.liquidform.internal.managedobjects.SimpleAggregateExpression.Function.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;

import com.google.code.liquidform.internal.AliasFactory;
import com.google.code.liquidform.internal.FrameworkObjectFactory;
import com.google.code.liquidform.internal.managedobjects.DistinctExpression;
import com.google.code.liquidform.internal.managedobjects.FrameworkManagedObject;
import com.google.code.liquidform.internal.managedobjects.SimpleAggregateExpression;

/**
 * Provides several constructs for recording the use of standard aggregate
 * functions. The function names here should be self explanatory and are
 * borrowed from the JPA query syntax.
 * 
 * <p>
 * The return types of those functions is specified by section 4.8.4 of JSR220
 * </p>
 * 
 */
public final class Aggregates {

	private Aggregates() {
		// utility class
	}

	private static final FrameworkObjectFactory factory = FrameworkObjectFactory.INSTANCE;

	public static <N extends Number> Double avg(N what) {
		// TODO maybe handle constants
		Double ret = factory.makeNew(Double.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(AVG, aboutArg));
		return ret;

	}

	public static <T> T distinct(T what) {
		T ret = factory.makeNew(what);
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory.remember(ret, new DistinctExpression(aboutArg));
		return ret;
	}

	public static Long sum(Number n) {
		Long ret = factory.makeNew(Long.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(n);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(SUM, aboutArg));
		return ret;
	}

	public static BigInteger sum(BigInteger n) {
		BigInteger ret = factory.makeNew(BigInteger.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(n);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(SUM, aboutArg));
		return ret;
	}

	public static BigDecimal sum(BigDecimal n) {
		BigDecimal ret = factory.makeNew(BigDecimal.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(n);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(SUM, aboutArg));
		return ret;
	}

	public static Double sum(Double n) {
		Double ret = factory.makeNew(Double.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(n);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(SUM, aboutArg));
		return ret;
	}

	public static Double sum(Float n) {
		Double ret = factory.makeNew(Double.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(n);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(SUM, aboutArg));
		return ret;
	}

	// See spec 4.8.4
	public static <N extends Number> N min(N what) {
		N ret = (N) factory.makeNew(what.getClass());
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MIN, aboutArg));
		return ret;
	}

	public static <N extends Number> N max(N what) {
		N ret = (N) factory.makeNew(what.getClass());
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MAX, aboutArg));
		return ret;
	}

	public static String min(String what) {
		String ret = factory.makeNew(String.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MIN, aboutArg));
		return ret;
	}

	public static String max(String what) {
		String ret = factory.makeNew(String.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MAX, aboutArg));
		return ret;
	}

	public static <D extends Date> D min(D what) {
		D ret = (D) factory.makeNew(what.getClass());
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MIN, aboutArg));
		return ret;
	}

	public static <D extends Date> D max(D what) {
		D ret = (D) factory.makeNew(what.getClass());
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MAX, aboutArg));
		return ret;
	}

	public static Character min(Character what) {
		Character ret = factory.makeNew(Character.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MIN, aboutArg));
		return ret;
	}

	public static Character max(Character what) {
		Character ret = factory.makeNew(Character.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(what);
		AliasFactory
				.remember(ret, new SimpleAggregateExpression(MAX, aboutArg));
		return ret;
	}

	public static Long count(Object o) {
		Long ret = factory.makeNew(Long.class);
		FrameworkManagedObject aboutArg = AliasFactory.about(o);
		AliasFactory.remember(ret, new SimpleAggregateExpression(COUNT,
				aboutArg));
		return ret;
	}

	public static Object star() {
		Long ret = factory.makeNew(Long.class);
		AliasFactory.remember(ret, new FrameworkManagedObject() {
			@Override
			public String getTargetRepresentation() {
				return "*";
			}
		});
		return ret;
	}

}
